package aoyebobi.minio.util;

import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description
 * @Author aoyebobi
 * @Date 2024-11-25
 */
@Component
@Slf4j
public class MinioUtil {

    /**
     * 实际使用时   要根据需求进行异常的抛出和捕获处理
     */
    @Resource
    private MinioClient minioClient;

    @Value("${minio.endpoint}")
    private String endpoint;

    /**
     * 分片大小 100M
     */
    private static final int CHUNK_SIZE = 1024 * 1024 * 100;

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5,
            10,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(100),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public String chunkUpload(MultipartFile file,String md5,String filePath,String bucketName) {
        log.info("文件开始分片");
        long fileTotalSize = file.getSize();
        int chunkCount = Math.toIntExact(fileTotalSize / CHUNK_SIZE);
        if(fileTotalSize % CHUNK_SIZE!= 0){
            chunkCount++;
        }
        CountDownLatch countDownLatch = new CountDownLatch(chunkCount);
        List<String> partList = new ArrayList<>();
        List<InputStream> partInputStreamList = new ArrayList<>();
        try(InputStream inputStream = file.getInputStream()){
            for (int i = 0; i < chunkCount; i++) {
                byte[] buffer = new byte[CHUNK_SIZE];
                int partInputStream = inputStream.read(buffer);
                partInputStreamList.add(new ByteArrayInputStream(buffer, 0, partInputStream));
            }
            int finalChunkCount = chunkCount;
            executor.submit(()->{
                for (int i = 0; i < finalChunkCount; i++) {
                    int chunkIndex = i + 1;
                    String chunkFilePath = filePath + "/chunks/" + md5 + ".part" + chunkIndex;
                    log.info("开始上传分块文件,分块序号:{},文件路径:{},桶名称:{}", chunkIndex, chunkFilePath, bucketName);
                    partList.add(chunkFilePath);
                    try {
                        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(chunkFilePath)
                                .stream(partInputStreamList.get(i), partInputStreamList.get(i).available(), -1).build());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    countDownLatch.countDown();
                }

            });
            countDownLatch.await();
        }catch (Exception e){
            log.error("分块上传失败,{}",e.getMessage());
        }
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        return merge(md5, chunkCount, suffix, filePath, bucketName,partList);
    }

    public String merge(String md5, Integer chunkTotal, String fileSuffix,
                        String filePath, String bucketName,List<String> partList) {
        log.info("开始合并分块文件,分块总数:{},文件后缀:{},文件路径:{},桶名称:{}", chunkTotal, fileSuffix, filePath, bucketName);
        // 获取缺失的分块
        List<Integer> missChunkIndexList = getMissChunkIndexList(partList, chunkTotal);
        if (!missChunkIndexList.isEmpty()) {
            log.error("分块丢失, 丢失的分块索引为: {}", missChunkIndexList);
            return null;
        }
        // 合并分块
        List<ComposeSource> composeSourceList = new ArrayList<>();
        for (String objectName : partList) {
            composeSourceList.add(ComposeSource.builder().bucket(bucketName).object(objectName).build());
        }
        String newFileName = filePath + "/" + md5 + fileSuffix;
        try {
            log.info("正在合并");
            minioClient.composeObject(
                    ComposeObjectArgs.builder().bucket(bucketName)
                            .object(newFileName)
                            .sources(composeSourceList).build());
        } catch (Exception e) {
            log.error("合并分块失败", e);
            throw new RuntimeException(String.format("minio 合并对象失败，【%s】", e.getMessage()));
        }
        // 获取文件url
        try {
            log.info("合并结束");
            return endpoint + "/" + bucketName + "/" + newFileName;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            // 删除临时文件
            partList.forEach(objectName -> {
                remove(objectName, bucketName);
            });
        }
    }

    /**
     * 获取缺失的分片
     *
     * @param chunkList  分片列表
     * @param chunkTotal 分片总数
     * @return 缺失的分片列表
     */
    private List<Integer> getMissChunkIndexList(List<String> chunkList, Integer chunkTotal) {
        // 缺失的分片文件索引
        List<Integer> missChunkIndexList = new ArrayList<>(chunkTotal);
        int index = 1;
        for (String chunkName : chunkList) {
            int chunkIndex = Integer.parseInt(chunkName.
                    substring(chunkName.lastIndexOf(".") + 1).replace("part", ""));
            if (index != chunkIndex) {
                missChunkIndexList.add(chunkIndex);
            }
            index++;
        }
        if (chunkTotal > index) {
            for (int i = index; i <= chunkTotal; i++) {
                missChunkIndexList.add(i);
            }
        }
        return missChunkIndexList;
    }

    /**
     *  拷贝文件夹
     * @author jlm
     * 2023/12/16 15:15
     * @param targetFolder 目标文件夹
     * @param targetBucket 目标桶名称
     * @param sourceFolder 源文件夹
     * @param sourceBucket  源桶名称
     * @return []
     */
    public List<String> copyFolder(String targetFolder, String targetBucket, String sourceFolder, String sourceBucket) {
        //查询当前目录所有文件
        List<Item> items = listObjects(sourceBucket, sourceFolder, true);
        ArrayList<String> failList = new ArrayList<>();
        for (Item item : items) {
            String sourceFile = item.objectName();
            String newFile = item.objectName().replace(sourceFolder, targetFolder);

            copyObject(newFile, targetBucket, sourceFile, sourceBucket);
        }
        return failList;
    }

    /**
     *  文件拷贝
     * @author jlm
     * 2023/12/16 15:14
     * @param targetFile 目标文件
     * @param targetBucket 目标桶名称
     * @param sourceFile 来源
     * @param sourceBucket 来源桶名称
     * @return String
     */
    public String copyObject(String targetFile,String targetBucket,String sourceFile,String   sourceBucket) {
        String newFile = "";
        try {
            ObjectWriteResponse objectWriteResponse = minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .bucket(targetBucket)
                            .object(targetFile)
                            .source(
                                    CopySource.builder()
                                            .bucket( sourceBucket)
                                            .object(sourceFile)
                                            .build())
                            .build());
            newFile = objectWriteResponse.object();

        } catch (Exception e) {
            log.error("getAllBuckets{}",e.getMessage());
        }
        return endpoint + "/" + targetBucket + "/" +newFile;
    }

    /**
     * 获取路径下文件列表
     *
     * @param bucketName 存储桶
     * @param prefix     文件名称
     * @param recursive  是否递归查找，false：模拟文件夹结构查找
     * @return 二进制流
     */
    public List<Item>  listObjects(String bucketName, String prefix, boolean recursive) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());
        List<Item> res = new ArrayList<>();
        if (results == null || !results.iterator().hasNext()) {
            return res;
        }
        results.forEach(itemResult -> {
            Item item = null;
            try {
                item = itemResult.get();
            } catch (Exception e) {
                log.info("查询文件异常:{}",e.getMessage());
            }
            res.add(item);
        });
        return res;
    }

    /**
     * 简单文件上传
     * @param bucketName 桶名
     * @param fileName 文件名
     * @param filePath 文件minio路径
     * @param file 文件
     * @return 文件的minion地址
     */
    public String upload(String bucketName,String fileName,String filePath,MultipartFile file){

        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        if(fileName == null){
            fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + suffix;
        }
        String objectName = filePath + "/" + fileName;
        return upload(bucketName,objectName,file);
    }

    public String upload(String bucketName, String objectName, MultipartFile file){
        try {
            InputStream inputStream = file.getInputStream();
            long size = file.getSize();
            long partSize = 10485760;
            String contentType = file.getContentType();
            upload(bucketName, objectName, inputStream, size, partSize, contentType);
            return endpoint + "/" + bucketName + "/" + objectName;
        } catch (Exception e) {
            log.error("上传文件失败:{}", e.getMessage());
            throw new RuntimeException("上传文件失败:" + e.getMessage());
        }
    }

    public boolean createBucketIfNotExist(String bucketName) {
        if (existBucket(bucketName)) {
            return true;
        } else {
            return createBucket(bucketName);
        }
    }

    public boolean deleteBucketIfExist(String bucketName) {
        if (existBucket(bucketName)) {
            return deleteBucket(bucketName);
        }
        return false;
    }

    /**
     * 删除
     */
    private void remove(String fileName, String bucketName){
        try {
            minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void upload(String bucketName, String objectName, InputStream inputStream,
                           long size,long partSize,String contentType) {
        try {
            minioClient.putObject(PutObjectArgs.builder()
                   .bucket(bucketName)
                    .object(objectName)
                    .stream(inputStream,size,partSize)
                    .contentType(contentType)
                    .build());
        }catch (Exception e){
            log.error("上传文件失败:{}", e.getMessage());
        }
    }

    private boolean deleteBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("删除桶失败:{}", e.getMessage());
            return false;
        }
        return true;
    }

    private boolean existBucket(String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("查询桶是否存在失败:{}", e.getMessage());
            return false;
        }
    }

    private boolean createBucket(String bucketName) {
        try {
            //设置桶为public状态
            String policy = "{\"Version\":\"2012-10-17\"," +
                    "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" +
                    "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," +
                    "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName +
                    "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" +
                    bucketName +
                    "/*\"]}]}";
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policy).build());
        } catch (Exception e) {
            log.error("创建桶失败:{}",e.getMessage());
            return false;
        }
        return true;
    }

}
